@s-hirano-ist/s-scripts 1.26.8 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/chunk-6ADTNHCK.js +85 -0
- package/dist/chunk-7PVU2Z32.js +71 -0
- package/dist/chunk-AJMTRCPJ.js +15 -0
- package/dist/chunk-AUD3PD6K.js +85 -0
- package/dist/chunk-F4J6JBR4.js +115 -0
- package/dist/chunk-HDYQV2Y2.js +58 -0
- package/dist/chunk-J4VLH4VA.js +267 -0
- package/dist/chunk-JZOR5GA2.js +42 -0
- package/dist/chunk-KKZP7ZF3.js +77 -0
- package/dist/chunk-M275YZQP.js +7723 -0
- package/dist/chunk-Q6QUV5VR.js +102 -0
- package/dist/chunk-TK456CR2.js +85 -0
- package/dist/chunk-UXGLXHQ6.js +40 -0
- package/dist/chunk-XG6LAGZO.js +590 -0
- package/dist/cleanup-minio-images.js +115 -92
- package/dist/enrich-books.js +87 -78
- package/dist/fetch-articles.js +101 -90
- package/dist/fetch-books.js +147 -128
- package/dist/fetch-images.js +88 -72
- package/dist/fetch-notes.js +66 -54
- package/dist/find-duplicate-json-articles.js +100 -97
- package/dist/fix-book-frontmatter.js +111 -99
- package/dist/fix-image-formats.js +81 -65
- package/dist/ingest-articles.js +30 -249
- package/dist/ingest-books.js +61 -318
- package/dist/ingest-images.js +33 -184
- package/dist/ingest-notes.js +41 -171
- package/dist/rag/delete-collection.js +32 -22
- package/dist/rag/ingest.js +371 -92
- package/dist/rag/search.js +80 -76
- package/dist/reset-articles.js +57 -60
- package/dist/reset-books.js +57 -59
- package/dist/reset-images.js +57 -59
- package/dist/reset-notes.js +57 -59
- package/dist/revert-articles.js +53 -55
- package/dist/revert-books.js +53 -53
- package/dist/revert-images.js +53 -53
- package/dist/revert-notes.js +53 -53
- package/dist/s-content-format.js +103 -13
- package/dist/update-json-articles.js +101 -97
- package/dist/update-raw-articles.js +130 -130
- package/dist/validate-json-articles.js +129 -130
- package/package.json +13 -50
- package/dist/cleanup-minio-images.d.ts +0 -3
- package/dist/cleanup-minio-images.d.ts.map +0 -1
- package/dist/cleanup-minio-images.js.map +0 -1
- package/dist/enrich-books.d.ts +0 -3
- package/dist/enrich-books.d.ts.map +0 -1
- package/dist/enrich-books.js.map +0 -1
- package/dist/fetch-articles.d.ts +0 -3
- package/dist/fetch-articles.d.ts.map +0 -1
- package/dist/fetch-articles.js.map +0 -1
- package/dist/fetch-books.d.ts +0 -3
- package/dist/fetch-books.d.ts.map +0 -1
- package/dist/fetch-books.js.map +0 -1
- package/dist/fetch-images.d.ts +0 -3
- package/dist/fetch-images.d.ts.map +0 -1
- package/dist/fetch-images.js.map +0 -1
- package/dist/fetch-notes.d.ts +0 -3
- package/dist/fetch-notes.d.ts.map +0 -1
- package/dist/fetch-notes.js.map +0 -1
- package/dist/find-duplicate-json-articles.d.ts +0 -3
- package/dist/find-duplicate-json-articles.d.ts.map +0 -1
- package/dist/find-duplicate-json-articles.js.map +0 -1
- package/dist/fix-book-frontmatter.d.ts +0 -3
- package/dist/fix-book-frontmatter.d.ts.map +0 -1
- package/dist/fix-book-frontmatter.js.map +0 -1
- package/dist/fix-image-formats.d.ts +0 -3
- package/dist/fix-image-formats.d.ts.map +0 -1
- package/dist/fix-image-formats.js.map +0 -1
- package/dist/infrastructures/articles-command-repository.d.ts +0 -57
- package/dist/infrastructures/articles-command-repository.d.ts.map +0 -1
- package/dist/infrastructures/articles-command-repository.js +0 -68
- package/dist/infrastructures/articles-command-repository.js.map +0 -1
- package/dist/infrastructures/books-command-repository.d.ts +0 -34
- package/dist/infrastructures/books-command-repository.d.ts.map +0 -1
- package/dist/infrastructures/books-command-repository.js +0 -53
- package/dist/infrastructures/books-command-repository.js.map +0 -1
- package/dist/infrastructures/cache-invalidation-client.d.ts +0 -9
- package/dist/infrastructures/cache-invalidation-client.d.ts.map +0 -1
- package/dist/infrastructures/cache-invalidation-client.js +0 -21
- package/dist/infrastructures/cache-invalidation-client.js.map +0 -1
- package/dist/infrastructures/cache-invalidation-client.test.d.ts +0 -2
- package/dist/infrastructures/cache-invalidation-client.test.d.ts.map +0 -1
- package/dist/infrastructures/cache-invalidation-client.test.js +0 -25
- package/dist/infrastructures/cache-invalidation-client.test.js.map +0 -1
- package/dist/infrastructures/images-command-repository.d.ts +0 -34
- package/dist/infrastructures/images-command-repository.d.ts.map +0 -1
- package/dist/infrastructures/images-command-repository.js +0 -53
- package/dist/infrastructures/images-command-repository.js.map +0 -1
- package/dist/infrastructures/notes-command-repository.d.ts +0 -34
- package/dist/infrastructures/notes-command-repository.d.ts.map +0 -1
- package/dist/infrastructures/notes-command-repository.js +0 -53
- package/dist/infrastructures/notes-command-repository.js.map +0 -1
- package/dist/ingest-articles.d.ts +0 -3
- package/dist/ingest-articles.d.ts.map +0 -1
- package/dist/ingest-articles.js.map +0 -1
- package/dist/ingest-books.d.ts +0 -3
- package/dist/ingest-books.d.ts.map +0 -1
- package/dist/ingest-books.js.map +0 -1
- package/dist/ingest-images.d.ts +0 -3
- package/dist/ingest-images.d.ts.map +0 -1
- package/dist/ingest-images.js.map +0 -1
- package/dist/ingest-notes.d.ts +0 -3
- package/dist/ingest-notes.d.ts.map +0 -1
- package/dist/ingest-notes.js.map +0 -1
- package/dist/lib/glob-paths.d.ts +0 -6
- package/dist/lib/glob-paths.d.ts.map +0 -1
- package/dist/lib/glob-paths.js +0 -9
- package/dist/lib/glob-paths.js.map +0 -1
- package/dist/lib/glob-paths.test.d.ts +0 -2
- package/dist/lib/glob-paths.test.d.ts.map +0 -1
- package/dist/lib/glob-paths.test.js +0 -30
- package/dist/lib/glob-paths.test.js.map +0 -1
- package/dist/lib/html-charset.d.ts +0 -4
- package/dist/lib/html-charset.d.ts.map +0 -1
- package/dist/lib/html-charset.js +0 -33
- package/dist/lib/html-charset.js.map +0 -1
- package/dist/lib/html-charset.test.d.ts +0 -2
- package/dist/lib/html-charset.test.d.ts.map +0 -1
- package/dist/lib/html-charset.test.js +0 -27
- package/dist/lib/html-charset.test.js.map +0 -1
- package/dist/rag/delete-collection.d.ts +0 -3
- package/dist/rag/delete-collection.d.ts.map +0 -1
- package/dist/rag/delete-collection.js.map +0 -1
- package/dist/rag/ingest-config.d.ts +0 -8
- package/dist/rag/ingest-config.d.ts.map +0 -1
- package/dist/rag/ingest-config.js +0 -8
- package/dist/rag/ingest-config.js.map +0 -1
- package/dist/rag/ingest.d.ts +0 -3
- package/dist/rag/ingest.d.ts.map +0 -1
- package/dist/rag/ingest.js.map +0 -1
- package/dist/rag/search.d.ts +0 -3
- package/dist/rag/search.d.ts.map +0 -1
- package/dist/rag/search.js.map +0 -1
- package/dist/reset-articles.d.ts +0 -3
- package/dist/reset-articles.d.ts.map +0 -1
- package/dist/reset-articles.js.map +0 -1
- package/dist/reset-books.d.ts +0 -3
- package/dist/reset-books.d.ts.map +0 -1
- package/dist/reset-books.js.map +0 -1
- package/dist/reset-images.d.ts +0 -3
- package/dist/reset-images.d.ts.map +0 -1
- package/dist/reset-images.js.map +0 -1
- package/dist/reset-notes.d.ts +0 -3
- package/dist/reset-notes.d.ts.map +0 -1
- package/dist/reset-notes.js.map +0 -1
- package/dist/revert-articles.d.ts +0 -3
- package/dist/revert-articles.d.ts.map +0 -1
- package/dist/revert-articles.js.map +0 -1
- package/dist/revert-books.d.ts +0 -3
- package/dist/revert-books.d.ts.map +0 -1
- package/dist/revert-books.js.map +0 -1
- package/dist/revert-images.d.ts +0 -3
- package/dist/revert-images.d.ts.map +0 -1
- package/dist/revert-images.js.map +0 -1
- package/dist/revert-notes.d.ts +0 -3
- package/dist/revert-notes.d.ts.map +0 -1
- package/dist/revert-notes.js.map +0 -1
- package/dist/s-content-format-runner.d.ts +0 -10
- package/dist/s-content-format-runner.d.ts.map +0 -1
- package/dist/s-content-format-runner.js +0 -76
- package/dist/s-content-format-runner.js.map +0 -1
- package/dist/s-content-format.d.ts +0 -3
- package/dist/s-content-format.d.ts.map +0 -1
- package/dist/s-content-format.js.map +0 -1
- package/dist/s-content-format.test.d.ts +0 -2
- package/dist/s-content-format.test.d.ts.map +0 -1
- package/dist/s-content-format.test.js +0 -99
- package/dist/s-content-format.test.js.map +0 -1
- package/dist/update-json-articles.d.ts +0 -3
- package/dist/update-json-articles.d.ts.map +0 -1
- package/dist/update-json-articles.js.map +0 -1
- package/dist/update-raw-articles.d.ts +0 -3
- package/dist/update-raw-articles.d.ts.map +0 -1
- package/dist/update-raw-articles.js.map +0 -1
- package/dist/validate-json-articles.d.ts +0 -3
- package/dist/validate-json-articles.d.ts.map +0 -1
- package/dist/validate-json-articles.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
CLI scripts for s-private data operations.
|
|
4
4
|
|
|
5
|
+
The npm artifact bundles the required internal workspace code and generated Prisma client. Consumers install only this CLI package; internal s-private packages are not separate npm dependencies. Command names remain unchanged.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```sh
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
makeExportedAt,
|
|
4
|
+
withTransactionRetry
|
|
5
|
+
} from "./chunk-M275YZQP.js";
|
|
6
|
+
|
|
7
|
+
// ../application/dist/batch/notes-batch-service.js
|
|
8
|
+
var NotesBatchApplicationService = class {
|
|
9
|
+
commandRepository;
|
|
10
|
+
constructor(commandRepository) {
|
|
11
|
+
this.commandRepository = commandRepository;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resets notes for a new batch export.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Delegates to repository's resetStatus which performs two operations
|
|
18
|
+
* atomically within a single transaction.
|
|
19
|
+
*/
|
|
20
|
+
async resetNotes(userId) {
|
|
21
|
+
return this.commandRepository.resetStatus(userId, makeExportedAt());
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reverts notes from LAST_UPDATED back to UNEXPORTED.
|
|
25
|
+
*/
|
|
26
|
+
async revertNotes(userId) {
|
|
27
|
+
return this.commandRepository.bulkUpdateStatus({
|
|
28
|
+
userId,
|
|
29
|
+
fromStatus: "LAST_UPDATED",
|
|
30
|
+
toStatus: "UNEXPORTED"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ../infrastructure/dist/batch/notes-command-repository.js
|
|
36
|
+
function createNotesCommandRepository(prisma) {
|
|
37
|
+
return {
|
|
38
|
+
async bulkUpdateStatus(params) {
|
|
39
|
+
const { userId, fromStatus, toStatus, exportedAt } = params;
|
|
40
|
+
const result = await prisma.note.updateMany({
|
|
41
|
+
where: {
|
|
42
|
+
userId,
|
|
43
|
+
status: fromStatus
|
|
44
|
+
},
|
|
45
|
+
data: {
|
|
46
|
+
status: toStatus,
|
|
47
|
+
...exportedAt && { exportedAt }
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return { count: result.count };
|
|
51
|
+
},
|
|
52
|
+
async resetStatus(userId, exportedAt) {
|
|
53
|
+
const [finalized, marked] = await withTransactionRetry(() => prisma.$transaction([
|
|
54
|
+
prisma.note.updateMany({
|
|
55
|
+
where: {
|
|
56
|
+
userId,
|
|
57
|
+
status: "LAST_UPDATED"
|
|
58
|
+
},
|
|
59
|
+
data: {
|
|
60
|
+
status: "EXPORTED",
|
|
61
|
+
exportedAt
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
prisma.note.updateMany({
|
|
65
|
+
where: {
|
|
66
|
+
userId,
|
|
67
|
+
status: "UNEXPORTED"
|
|
68
|
+
},
|
|
69
|
+
data: {
|
|
70
|
+
status: "LAST_UPDATED"
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
]));
|
|
74
|
+
return {
|
|
75
|
+
finalized: { count: finalized.count },
|
|
76
|
+
marked: { count: marked.count }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
NotesBatchApplicationService,
|
|
84
|
+
createNotesCommandRepository
|
|
85
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../notification/dist/errors.js
|
|
4
|
+
var NotificationError = class extends Error {
|
|
5
|
+
constructor(message = "notificationSend") {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "NotificationError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// ../notification/dist/pushover.js
|
|
12
|
+
var PushoverService = class {
|
|
13
|
+
config;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sends a message to the Pushover API.
|
|
19
|
+
*
|
|
20
|
+
* @param message - The message to send
|
|
21
|
+
* @param priority - The priority level (-1 = low, 0 = normal, 1 = high)
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
async sendMessage(message, priority = 0) {
|
|
26
|
+
try {
|
|
27
|
+
const body = new URLSearchParams({
|
|
28
|
+
token: this.config.appToken,
|
|
29
|
+
user: this.config.userKey,
|
|
30
|
+
message,
|
|
31
|
+
priority: String(priority)
|
|
32
|
+
});
|
|
33
|
+
const result = await fetch(this.config.url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
36
|
+
body
|
|
37
|
+
});
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
throw new NotificationError();
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof NotificationError) {
|
|
43
|
+
console.error(`[NotificationService] Failed to send notification: ${error.message}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.error("[NotificationService] Unknown error occurred while sending notification:", error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async notifyError(message, context) {
|
|
50
|
+
const enrichedMessage = `[ERROR] ${context.caller}: ${message}`;
|
|
51
|
+
await this.sendMessage(enrichedMessage, 1);
|
|
52
|
+
}
|
|
53
|
+
async notifyWarning(message, context) {
|
|
54
|
+
const enrichedMessage = `[WARN] ${context.caller}: ${message}`;
|
|
55
|
+
await this.sendMessage(enrichedMessage, 0);
|
|
56
|
+
}
|
|
57
|
+
async notifyInfo(message, context) {
|
|
58
|
+
const enrichedMessage = `[INFO] ${context.caller}: ${message}`;
|
|
59
|
+
await this.sendMessage(enrichedMessage, -1);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
function createPushoverService(config) {
|
|
63
|
+
return new PushoverService(config);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ../notification/dist/web-push.js
|
|
67
|
+
import webPush from "web-push";
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
createPushoverService
|
|
71
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/lib/glob-paths.ts
|
|
4
|
+
import { glob } from "node:fs/promises";
|
|
5
|
+
async function globPaths(pattern, options = {}) {
|
|
6
|
+
const paths = [];
|
|
7
|
+
for await (const path of glob(pattern, options)) {
|
|
8
|
+
paths.push(path);
|
|
9
|
+
}
|
|
10
|
+
return paths.toSorted((left, right) => left.localeCompare(right, "en"));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
globPaths
|
|
15
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
makeExportedAt,
|
|
4
|
+
withTransactionRetry
|
|
5
|
+
} from "./chunk-M275YZQP.js";
|
|
6
|
+
|
|
7
|
+
// ../application/dist/batch/books-batch-service.js
|
|
8
|
+
var BooksBatchApplicationService = class {
|
|
9
|
+
commandRepository;
|
|
10
|
+
constructor(commandRepository) {
|
|
11
|
+
this.commandRepository = commandRepository;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resets books for a new batch export.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Delegates to repository's resetStatus which performs two operations
|
|
18
|
+
* atomically within a single transaction.
|
|
19
|
+
*/
|
|
20
|
+
async resetBooks(userId) {
|
|
21
|
+
return this.commandRepository.resetStatus(userId, makeExportedAt());
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reverts books from LAST_UPDATED back to UNEXPORTED.
|
|
25
|
+
*/
|
|
26
|
+
async revertBooks(userId) {
|
|
27
|
+
return this.commandRepository.bulkUpdateStatus({
|
|
28
|
+
userId,
|
|
29
|
+
fromStatus: "LAST_UPDATED",
|
|
30
|
+
toStatus: "UNEXPORTED"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ../infrastructure/dist/batch/books-command-repository.js
|
|
36
|
+
function createBooksCommandRepository(prisma) {
|
|
37
|
+
return {
|
|
38
|
+
async bulkUpdateStatus(params) {
|
|
39
|
+
const { userId, fromStatus, toStatus, exportedAt } = params;
|
|
40
|
+
const result = await prisma.book.updateMany({
|
|
41
|
+
where: {
|
|
42
|
+
userId,
|
|
43
|
+
status: fromStatus
|
|
44
|
+
},
|
|
45
|
+
data: {
|
|
46
|
+
status: toStatus,
|
|
47
|
+
...exportedAt && { exportedAt }
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return { count: result.count };
|
|
51
|
+
},
|
|
52
|
+
async resetStatus(userId, exportedAt) {
|
|
53
|
+
const [finalized, marked] = await withTransactionRetry(() => prisma.$transaction([
|
|
54
|
+
prisma.book.updateMany({
|
|
55
|
+
where: {
|
|
56
|
+
userId,
|
|
57
|
+
status: "LAST_UPDATED"
|
|
58
|
+
},
|
|
59
|
+
data: {
|
|
60
|
+
status: "EXPORTED",
|
|
61
|
+
exportedAt
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
prisma.book.updateMany({
|
|
65
|
+
where: {
|
|
66
|
+
userId,
|
|
67
|
+
status: "UNEXPORTED"
|
|
68
|
+
},
|
|
69
|
+
data: {
|
|
70
|
+
status: "LAST_UPDATED"
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
]));
|
|
74
|
+
return {
|
|
75
|
+
finalized: { count: finalized.count },
|
|
76
|
+
marked: { count: marked.count }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
BooksBatchApplicationService,
|
|
84
|
+
createBooksCommandRepository
|
|
85
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../storage/dist/src/client.js
|
|
4
|
+
import { Client } from "minio";
|
|
5
|
+
|
|
6
|
+
// ../storage/dist/src/cf-access-transport.js
|
|
7
|
+
import * as https from "node:https";
|
|
8
|
+
function createCfAccessTransport(config) {
|
|
9
|
+
if (!config?.clientId || !config.clientSecret) {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
request(options, ...rest) {
|
|
14
|
+
if (typeof options === "object" && !(options instanceof URL)) {
|
|
15
|
+
const headers = options.headers ?? {};
|
|
16
|
+
headers["CF-Access-Client-Id"] = config.clientId;
|
|
17
|
+
headers["CF-Access-Client-Secret"] = config.clientSecret;
|
|
18
|
+
options.headers = headers;
|
|
19
|
+
}
|
|
20
|
+
const request2 = https.request;
|
|
21
|
+
return request2(options, ...rest);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ../storage/dist/src/client.js
|
|
27
|
+
function createMinioClient(config, cfAccess) {
|
|
28
|
+
const resolved = config ?? {
|
|
29
|
+
endPoint: process.env.MINIO_HOST ?? "",
|
|
30
|
+
port: Number(process.env.MINIO_PORT),
|
|
31
|
+
useSSL: process.env.MINIO_USE_SSL === void 0 ? !["localhost", "127.0.0.1"].includes(process.env.MINIO_HOST ?? "") : process.env.MINIO_USE_SSL === "true",
|
|
32
|
+
accessKey: process.env.MINIO_ACCESS_KEY ?? "",
|
|
33
|
+
secretKey: process.env.MINIO_SECRET_KEY ?? ""
|
|
34
|
+
};
|
|
35
|
+
const resolvedCfAccess = cfAccess ?? {
|
|
36
|
+
clientId: process.env.CF_ACCESS_CLIENT_ID ?? "",
|
|
37
|
+
clientSecret: process.env.CF_ACCESS_CLIENT_SECRET ?? ""
|
|
38
|
+
};
|
|
39
|
+
const transport = resolved.useSSL ? createCfAccessTransport(resolvedCfAccess) : void 0;
|
|
40
|
+
if (resolved.useSSL && !transport) {
|
|
41
|
+
throw new Error("CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET are required when useSSL is true.");
|
|
42
|
+
}
|
|
43
|
+
return new Client({
|
|
44
|
+
endPoint: resolved.endPoint,
|
|
45
|
+
port: resolved.port,
|
|
46
|
+
useSSL: resolved.useSSL,
|
|
47
|
+
accessKey: resolved.accessKey,
|
|
48
|
+
secretKey: resolved.secretKey,
|
|
49
|
+
...transport && { transport }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ../storage/dist/index.js
|
|
54
|
+
import { S3Error } from "minio";
|
|
55
|
+
|
|
56
|
+
// ../storage/dist/src/errors.js
|
|
57
|
+
var StorageOperationError = class extends Error {
|
|
58
|
+
context;
|
|
59
|
+
constructor(context, cause) {
|
|
60
|
+
super(`Storage ${context.operation} failed for ${context.objectKey}`, {
|
|
61
|
+
cause
|
|
62
|
+
});
|
|
63
|
+
this.name = "StorageOperationError";
|
|
64
|
+
this.context = context;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ../storage/dist/src/storage-service.js
|
|
69
|
+
function createStorageService(client, bucketName, pathConfig) {
|
|
70
|
+
function buildObjectKey(path, isThumbnail) {
|
|
71
|
+
return `${isThumbnail ? pathConfig.thumbnailPrefix : pathConfig.originalPrefix}/${path}`;
|
|
72
|
+
}
|
|
73
|
+
function wrapStorageError(operation, objectKey, isThumbnail, error) {
|
|
74
|
+
throw new StorageOperationError({ operation, objectKey, bucketName, isThumbnail }, error);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
async uploadImage(path, bytes, isThumbnail) {
|
|
78
|
+
const objKey = buildObjectKey(path, isThumbnail);
|
|
79
|
+
try {
|
|
80
|
+
await client.putObject(bucketName, objKey, Buffer.from(bytes));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
wrapStorageError("uploadImage", objKey, isThumbnail, error);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async getImage(path, isThumbnail) {
|
|
86
|
+
const objKey = buildObjectKey(path, isThumbnail);
|
|
87
|
+
try {
|
|
88
|
+
return await client.getObject(bucketName, objKey);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return wrapStorageError("getImage", objKey, isThumbnail, error);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
async getImageOrThrow(path, isThumbnail) {
|
|
94
|
+
const objKey = buildObjectKey(path, isThumbnail);
|
|
95
|
+
try {
|
|
96
|
+
await client.statObject(bucketName, objKey);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
wrapStorageError("getImageOrThrow", objKey, isThumbnail, error);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async deleteImage(path, isThumbnail) {
|
|
102
|
+
const objKey = buildObjectKey(path, isThumbnail);
|
|
103
|
+
try {
|
|
104
|
+
await client.removeObject(bucketName, objKey);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
wrapStorageError("deleteImage", objKey, isThumbnail, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
createMinioClient,
|
|
114
|
+
createStorageService
|
|
115
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../search/dist/frontmatter.js
|
|
4
|
+
import { load } from "js-yaml";
|
|
5
|
+
var BYTE_ORDER_MARK = "\uFEFF";
|
|
6
|
+
var OPENING_DELIMITER_REGEX = /^---[\t ]*\r?\n/u;
|
|
7
|
+
var CLOSING_DELIMITER_REGEX = /^---[\t ]*\r?$/gmu;
|
|
8
|
+
function stripByteOrderMark(source) {
|
|
9
|
+
return source.startsWith(BYTE_ORDER_MARK) ? source.slice(1) : source;
|
|
10
|
+
}
|
|
11
|
+
function getContentStart(source, delimiterEnd) {
|
|
12
|
+
if (source.startsWith("\r\n", delimiterEnd)) {
|
|
13
|
+
return delimiterEnd + 2;
|
|
14
|
+
}
|
|
15
|
+
if (source.startsWith("\n", delimiterEnd)) {
|
|
16
|
+
return delimiterEnd + 1;
|
|
17
|
+
}
|
|
18
|
+
return delimiterEnd;
|
|
19
|
+
}
|
|
20
|
+
function parseYamlObject(source) {
|
|
21
|
+
if (source.trim() === "") {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const parsed = load(source);
|
|
25
|
+
if (parsed === void 0) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
29
|
+
throw new Error("Markdown frontmatter must be a YAML object.");
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
function parseMarkdownFrontmatter(source) {
|
|
34
|
+
const normalizedSource = stripByteOrderMark(source);
|
|
35
|
+
const openingDelimiter = OPENING_DELIMITER_REGEX.exec(normalizedSource);
|
|
36
|
+
if (!openingDelimiter) {
|
|
37
|
+
if (/^---[\t ]*$/u.test(normalizedSource)) {
|
|
38
|
+
throw new Error("Markdown frontmatter is missing a closing delimiter.");
|
|
39
|
+
}
|
|
40
|
+
return { content: normalizedSource, data: {} };
|
|
41
|
+
}
|
|
42
|
+
CLOSING_DELIMITER_REGEX.lastIndex = openingDelimiter[0].length;
|
|
43
|
+
const closingDelimiter = CLOSING_DELIMITER_REGEX.exec(normalizedSource);
|
|
44
|
+
CLOSING_DELIMITER_REGEX.lastIndex = 0;
|
|
45
|
+
if (!closingDelimiter) {
|
|
46
|
+
throw new Error("Markdown frontmatter is missing a closing delimiter.");
|
|
47
|
+
}
|
|
48
|
+
const yamlSource = normalizedSource.slice(openingDelimiter[0].length, closingDelimiter.index);
|
|
49
|
+
const contentStart = getContentStart(normalizedSource, closingDelimiter.index + closingDelimiter[0].length);
|
|
50
|
+
return {
|
|
51
|
+
content: normalizedSource.slice(contentStart),
|
|
52
|
+
data: parseYamlObject(yamlSource)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
parseMarkdownFrontmatter
|
|
58
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../search/dist/qdrant-client.js
|
|
4
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
5
|
+
|
|
6
|
+
// ../search/dist/config.js
|
|
7
|
+
function requireEnv(name) {
|
|
8
|
+
const value = process.env[name];
|
|
9
|
+
if (!value)
|
|
10
|
+
throw new Error(`${name} environment variable is required`);
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function envWithDefault(name, defaultValue) {
|
|
14
|
+
return process.env[name] || defaultValue;
|
|
15
|
+
}
|
|
16
|
+
var RAG_CONFIG = {
|
|
17
|
+
qdrant: {
|
|
18
|
+
collectionName: requireEnv("QDRANT_COLLECTION_NAME"),
|
|
19
|
+
vectorSize: Number(envWithDefault("EMBEDDING_VECTOR_SIZE", "384")),
|
|
20
|
+
distance: "Cosine"
|
|
21
|
+
},
|
|
22
|
+
embedding: {
|
|
23
|
+
// Deployed services and ingestion jobs always use Qdrant Cloud Inference.
|
|
24
|
+
// Keep TEI only for explicitly enabled, non-production local development.
|
|
25
|
+
url: process.env.LOCAL_DEV_MODE === "true" && process.env.VERCEL !== "1" && process.env.NODE_ENV !== "production" ? process.env.EMBEDDING_URL : void 0,
|
|
26
|
+
model: envWithDefault("EMBEDDING_MODEL", "intfloat/multilingual-e5-small")
|
|
27
|
+
},
|
|
28
|
+
chunking: { maxChunkLength: 2e3, headingLevels: [2, 3] }
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ../search/dist/qdrant-client.js
|
|
32
|
+
var client = null;
|
|
33
|
+
async function embedLocally(texts) {
|
|
34
|
+
const { url } = RAG_CONFIG.embedding;
|
|
35
|
+
if (!url)
|
|
36
|
+
throw new Error("EMBEDDING_URL environment variable is required");
|
|
37
|
+
const response = await fetch(`${url.replace(/\/$/u, "")}/embed`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "content-type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ inputs: texts, normalize: true, truncate: true })
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`Embedding request failed (${response.status}): ${await response.text()}`);
|
|
44
|
+
}
|
|
45
|
+
const embeddings = await response.json();
|
|
46
|
+
if (!Array.isArray(embeddings) || embeddings.length !== texts.length || embeddings.some((embedding) => !Array.isArray(embedding) || embedding.some((value) => typeof value !== "number"))) {
|
|
47
|
+
throw new TypeError("Embedding API returned an invalid response");
|
|
48
|
+
}
|
|
49
|
+
return embeddings;
|
|
50
|
+
}
|
|
51
|
+
function getQdrantClient(config) {
|
|
52
|
+
if (!client) {
|
|
53
|
+
const url = config?.url ?? process.env.QDRANT_URL;
|
|
54
|
+
const apiKey = config?.apiKey ?? process.env.QDRANT_API_KEY;
|
|
55
|
+
if (!url) {
|
|
56
|
+
throw new Error("QDRANT_URL environment variable is required");
|
|
57
|
+
}
|
|
58
|
+
client = new QdrantClient({
|
|
59
|
+
url,
|
|
60
|
+
apiKey
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return client;
|
|
64
|
+
}
|
|
65
|
+
async function deleteCollection(collectionName) {
|
|
66
|
+
const qdrant = getQdrantClient();
|
|
67
|
+
await qdrant.deleteCollection(collectionName);
|
|
68
|
+
}
|
|
69
|
+
async function ensureCollection() {
|
|
70
|
+
const qdrant = getQdrantClient();
|
|
71
|
+
const { collectionName, vectorSize, distance } = RAG_CONFIG.qdrant;
|
|
72
|
+
const collections = await qdrant.getCollections();
|
|
73
|
+
const exists = collections.collections.some((c) => c.name === collectionName);
|
|
74
|
+
if (exists) {
|
|
75
|
+
console.log(`Collection ${collectionName} already exists.`);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`Creating collection: ${collectionName}`);
|
|
78
|
+
await qdrant.createCollection(collectionName, {
|
|
79
|
+
vectors: {
|
|
80
|
+
size: vectorSize,
|
|
81
|
+
distance
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
console.log(`Collection ${collectionName} created successfully.`);
|
|
85
|
+
}
|
|
86
|
+
await qdrant.createPayloadIndex(collectionName, {
|
|
87
|
+
field_name: "type",
|
|
88
|
+
field_schema: "keyword",
|
|
89
|
+
wait: true
|
|
90
|
+
});
|
|
91
|
+
await qdrant.createPayloadIndex(collectionName, {
|
|
92
|
+
field_name: "top_heading",
|
|
93
|
+
field_schema: "keyword",
|
|
94
|
+
wait: true
|
|
95
|
+
});
|
|
96
|
+
await qdrant.createPayloadIndex(collectionName, {
|
|
97
|
+
field_name: "content_type",
|
|
98
|
+
field_schema: "keyword",
|
|
99
|
+
wait: true
|
|
100
|
+
});
|
|
101
|
+
console.log("Payload indexes ensured for: type, top_heading, content_type");
|
|
102
|
+
}
|
|
103
|
+
async function upsertPoints(points) {
|
|
104
|
+
const qdrant = getQdrantClient();
|
|
105
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
106
|
+
const localEmbeddings = RAG_CONFIG.embedding.url ? await embedLocally(points.map((point) => point.text)) : void 0;
|
|
107
|
+
const qdrantPoints = points.map((p, index) => ({
|
|
108
|
+
id: hashToUint(p.id),
|
|
109
|
+
vector: localEmbeddings?.[index] ?? {
|
|
110
|
+
text: p.text,
|
|
111
|
+
model: RAG_CONFIG.embedding.model
|
|
112
|
+
},
|
|
113
|
+
payload: p.payload
|
|
114
|
+
}));
|
|
115
|
+
await qdrant.upsert(collectionName, {
|
|
116
|
+
wait: true,
|
|
117
|
+
points: qdrantPoints
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function getExistingHashes(chunkIds) {
|
|
121
|
+
const qdrant = getQdrantClient();
|
|
122
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
123
|
+
const hashMap = /* @__PURE__ */ new Map();
|
|
124
|
+
if (chunkIds.length === 0)
|
|
125
|
+
return hashMap;
|
|
126
|
+
const numericIds = chunkIds.map((id) => hashToUint(id));
|
|
127
|
+
try {
|
|
128
|
+
const result = await qdrant.retrieve(collectionName, {
|
|
129
|
+
ids: numericIds,
|
|
130
|
+
with_payload: ["chunk_id", "content_hash"]
|
|
131
|
+
});
|
|
132
|
+
for (const point of result) {
|
|
133
|
+
const payload = point.payload;
|
|
134
|
+
if (payload?.chunk_id && payload?.content_hash) {
|
|
135
|
+
hashMap.set(payload.chunk_id, payload.content_hash);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
return hashMap;
|
|
141
|
+
}
|
|
142
|
+
async function search(queryText, options = {}) {
|
|
143
|
+
const qdrant = getQdrantClient();
|
|
144
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
145
|
+
const { topK = 10, filter } = options;
|
|
146
|
+
const filterConditions = [];
|
|
147
|
+
if (filter?.type) {
|
|
148
|
+
filterConditions.push({
|
|
149
|
+
key: "type",
|
|
150
|
+
match: { value: filter.type }
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (filter?.top_heading) {
|
|
154
|
+
filterConditions.push({
|
|
155
|
+
key: "top_heading",
|
|
156
|
+
match: { value: filter.top_heading }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (filter?.content_type) {
|
|
160
|
+
if (Array.isArray(filter.content_type)) {
|
|
161
|
+
filterConditions.push({
|
|
162
|
+
key: "content_type",
|
|
163
|
+
match: { any: filter.content_type }
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
filterConditions.push({
|
|
167
|
+
key: "content_type",
|
|
168
|
+
match: { value: filter.content_type }
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const query = RAG_CONFIG.embedding.url ? (await embedLocally([queryText]))[0] : { text: queryText, model: RAG_CONFIG.embedding.model };
|
|
173
|
+
const result = await qdrant.query(collectionName, {
|
|
174
|
+
query,
|
|
175
|
+
limit: topK,
|
|
176
|
+
with_payload: true,
|
|
177
|
+
filter: filterConditions.length > 0 ? { must: filterConditions } : void 0
|
|
178
|
+
});
|
|
179
|
+
return result.points.map((r) => {
|
|
180
|
+
const payload = r.payload;
|
|
181
|
+
return {
|
|
182
|
+
score: r.score,
|
|
183
|
+
text: payload.text,
|
|
184
|
+
title: payload.title,
|
|
185
|
+
url: payload.url,
|
|
186
|
+
heading_path: payload.heading_path,
|
|
187
|
+
type: payload.type,
|
|
188
|
+
content_type: payload.content_type,
|
|
189
|
+
doc_id: payload.doc_id
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function listAllChunkIds() {
|
|
194
|
+
const qdrant = getQdrantClient();
|
|
195
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
196
|
+
const chunkIds = [];
|
|
197
|
+
const pageSize = 256;
|
|
198
|
+
let nextOffset;
|
|
199
|
+
try {
|
|
200
|
+
do {
|
|
201
|
+
const result = await qdrant.scroll(collectionName, {
|
|
202
|
+
limit: pageSize,
|
|
203
|
+
offset: typeof nextOffset === "string" || typeof nextOffset === "number" ? nextOffset : void 0,
|
|
204
|
+
with_payload: ["chunk_id"],
|
|
205
|
+
with_vector: false
|
|
206
|
+
});
|
|
207
|
+
for (const point of result.points) {
|
|
208
|
+
const payload = point.payload;
|
|
209
|
+
if (payload?.chunk_id) {
|
|
210
|
+
chunkIds.push(payload.chunk_id);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
nextOffset = result.next_page_offset;
|
|
214
|
+
} while (nextOffset !== null && nextOffset !== void 0);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
return chunkIds;
|
|
218
|
+
}
|
|
219
|
+
async function deletePointsByChunkIds(chunkIds) {
|
|
220
|
+
if (chunkIds.length === 0)
|
|
221
|
+
return;
|
|
222
|
+
const qdrant = getQdrantClient();
|
|
223
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
224
|
+
const numericIds = chunkIds.map((id) => hashToUint(id));
|
|
225
|
+
await qdrant.delete(collectionName, {
|
|
226
|
+
wait: true,
|
|
227
|
+
points: numericIds
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async function getCollectionStats() {
|
|
231
|
+
const qdrant = getQdrantClient();
|
|
232
|
+
const { collectionName } = RAG_CONFIG.qdrant;
|
|
233
|
+
try {
|
|
234
|
+
const info = await qdrant.getCollection(collectionName);
|
|
235
|
+
return {
|
|
236
|
+
pointsCount: info.points_count ?? 0,
|
|
237
|
+
status: info.status
|
|
238
|
+
};
|
|
239
|
+
} catch {
|
|
240
|
+
return {
|
|
241
|
+
pointsCount: 0,
|
|
242
|
+
status: "not_found"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function hashToUint(str) {
|
|
247
|
+
let hash = 0;
|
|
248
|
+
for (let i = 0; i < str.length; i++) {
|
|
249
|
+
const char = str.codePointAt(i) ?? 0;
|
|
250
|
+
hash = (hash << 5) - hash + char;
|
|
251
|
+
hash = hash & hash;
|
|
252
|
+
}
|
|
253
|
+
return Math.abs(hash);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export {
|
|
257
|
+
RAG_CONFIG,
|
|
258
|
+
getQdrantClient,
|
|
259
|
+
deleteCollection,
|
|
260
|
+
ensureCollection,
|
|
261
|
+
upsertPoints,
|
|
262
|
+
getExistingHashes,
|
|
263
|
+
search,
|
|
264
|
+
listAllChunkIds,
|
|
265
|
+
deletePointsByChunkIds,
|
|
266
|
+
getCollectionStats
|
|
267
|
+
};
|